package com.yunhe.abnormal.service.base.impl;

import com.yunhe.common.constant.CacheConsts;
import com.yunhe.abnormal.domain.base.AlarmCalculation;
import com.yunhe.abnormal.domain.base.AlarmRuleInstance;
import com.yunhe.common.exception.ObjectNotFoundException;
import com.yunhe.abnormal.mapper.base.AlarmRuleInstanceMapper;
import com.yunhe.common.model.base.AlarmRuleInstanceDTO;
import com.yunhe.abnormal.repository.base.AlarmRuleInstanceRepository;
import com.yunhe.abnormal.repository.base.AlarmRuleInstanceSession;
import com.yunhe.abnormal.service.base.AlarmRuleInstanceService;
import com.yunhe.abnormal.transaction.MultiTransaction;
import com.yunhe.abnormal.transaction.Neo4jTransaction;
import com.yunhe.common.util.RedisClient;
import net.oschina.j2cache.cache.support.util.SpringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 异常规则实例Service
 * @author liuronglei
 */
@Service
public class AlarmRuleInstanceServiceImpl implements AlarmRuleInstanceService {
    @Autowired
    private AlarmRuleInstanceRepository alarmRuleInstanceRepository;
    @Autowired
    private AlarmRuleInstanceSession alarmRuleInstanceSession;
    @Autowired
    private AlarmRuleInstanceMapper mapper;
    @Autowired
    private RedisClient redisClient;

    /**
     * 获得用于保存时使用的异常规则实例对象（去掉关系，只保存属性）
     * @param alarmRuleInstance 异常规则实例对象
     */
    private AlarmRuleInstance getStoreInstance(AlarmRuleInstance alarmRuleInstance) {
        AlarmRuleInstance storeAlarmRuleInstance = new AlarmRuleInstance();
        storeAlarmRuleInstance.setId(alarmRuleInstance.getId());
        storeAlarmRuleInstance.setStationId(alarmRuleInstance.getStationId());
        storeAlarmRuleInstance.setFormula(alarmRuleInstance.getFormula());
        storeAlarmRuleInstance.setPoints(alarmRuleInstance.getPoints());
        storeAlarmRuleInstance.setRelatedPoints(alarmRuleInstance.getRelatedPoints());
        storeAlarmRuleInstance.setRelatedAlarmRuleId(alarmRuleInstance.getAlarmRule().getId());
        if (alarmRuleInstance.getStation() != null) {
            storeAlarmRuleInstance.setRelatedStationId(alarmRuleInstance.getStation().getId());
        }
        if (alarmRuleInstance.getDevice() != null) {
            storeAlarmRuleInstance.setRelatedDeviceId(alarmRuleInstance.getDevice().getId());
        }
        return storeAlarmRuleInstance;
    }

    /**
     * 删除一个对象
     * @param id 异常规则实例ID
     */
    private void deleteOne(Long id) {
        alarmRuleInstanceRepository.deleteById(id);
    }

    /**
     * 新增一个对象
     * @param alarmRuleInstance 异常规则实例
     */
    private AlarmRuleInstanceDTO createOne(AlarmRuleInstanceDTO alarmRuleInstance) {
        AlarmRuleInstance myInstance = mapper.from(alarmRuleInstance);
        AlarmRuleInstance newInstance = alarmRuleInstanceRepository.save(getStoreInstance(myInstance));
        myInstance.setId(newInstance.getId());
        alarmRuleInstanceSession.unBindRelat(myInstance.getId());
        alarmRuleInstanceSession.bindRelat(myInstance);
        return mapper.to(myInstance);
    }

    /**
     * 执行异常规则实例更新
     * @param alarmRuleInstance 异常规则实例
     */
    private AlarmRuleInstanceDTO updateOne(AlarmRuleInstanceDTO alarmRuleInstance) throws ObjectNotFoundException {
        AlarmRuleInstance byId = alarmRuleInstanceRepository.findById(alarmRuleInstance.getId()).orElse(null);
        if (byId == null) {
            throw new ObjectNotFoundException("找不到ID为" + alarmRuleInstance.getId() + "的异常规则实例");
        }
        AlarmRuleInstance myInstance = mapper.from(alarmRuleInstance);
        AlarmRuleInstance newRule = alarmRuleInstanceRepository.save(getStoreInstance(myInstance));
        myInstance.setId(newRule.getId());
        alarmRuleInstanceSession.unBindRelat(myInstance.getId());
        alarmRuleInstanceSession.bindRelat(myInstance);
        return mapper.to(myInstance);
    }

    /**
     * 对比异常规则实例对象和DTO是否是同一个适用设备
     * @param dto 异常规则实例DTO
     * @param item 异常规则实例对象
     */
    private boolean compareObj(AlarmRuleInstanceDTO dto, AlarmRuleInstance item) {
        if (dto != null && item != null) {
            if (dto.getDevice() != null && item.getDevice() != null) {
                return dto.getDevice().getId().equals(item.getDevice().getId());
            } else if (dto.getStation() != null && item.getStation() != null) {
                return dto.getStation().getId().equals(item.getStation().getId());
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public AlarmRuleInstanceDTO create(AlarmRuleInstanceDTO alarmRuleInstance) {
        return createOne(alarmRuleInstance);
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public List<AlarmRuleInstanceDTO> create(List<AlarmRuleInstanceDTO> alarmRuleInstances) {
        List<AlarmRuleInstanceDTO> result = new ArrayList<>();
        for (AlarmRuleInstanceDTO alarmRuleInstance : alarmRuleInstances) {
            result.add(createOne(alarmRuleInstance));
        }
        return result;
    }

    @Override
    @Cacheable(value = CacheConsts.Region.ALARM_RULE_INSTANCE,
            key = "{T(com.yunhe.abnormal.consts.AlarmRuleInstanceKey).getIdKey(#id)}")
    public AlarmRuleInstanceDTO findById(Long id) {
        Optional<AlarmRuleInstance> obj = alarmRuleInstanceRepository.findById(id);
        return mapper.to(obj.orElse(null));
    }

    @Override
    public List<AlarmRuleInstanceDTO> findByIds(List<Long> ids) {
        List<AlarmRuleInstance> instances = alarmRuleInstanceRepository.findByIds(ids);
        return mapper.to(instances);
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void deleteById(Long id) {
        deleteOne(id);
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public AlarmRuleInstanceDTO update(AlarmRuleInstanceDTO alarmRuleInstance) throws ObjectNotFoundException {
        return updateOne(alarmRuleInstance);
    }

    @Override
    @Neo4jTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public List<AlarmRuleInstanceDTO> update(List<AlarmRuleInstanceDTO> alarmRuleInstances) {
        List<AlarmRuleInstance> instances = mapper.from(alarmRuleInstances);
        List<AlarmRuleInstance> result = (List<AlarmRuleInstance>)alarmRuleInstanceRepository.save(instances, 0);
        return mapper.to(result);
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void batchDelete(List<Long> ids) {
        if (ids !=null && !ids.isEmpty()) {
            for (Long id : ids) {
                deleteOne(id);
            }
        }
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void deleteByRuleId(Long alarmRuleId) {
        alarmRuleInstanceSession.deleteByRuleId(alarmRuleId);
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void deleteByRuleId(List<Long> alarmRuleIds) {
        if (alarmRuleIds !=null && !alarmRuleIds.isEmpty()) {
            for (Long alarmRuleId : alarmRuleIds) {
                alarmRuleInstanceSession.deleteByRuleId(alarmRuleId);
            }
        }
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void deleteByStationId(List<Long> stationIds) {
        if (stationIds !=null && !stationIds.isEmpty()) {
            for (Long stationId : stationIds) {
                alarmRuleInstanceSession.deleteByStationId(stationId);
            }
        }
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void deleteByDeviceId(List<Long> deviceIds) {
        if (deviceIds !=null && !deviceIds.isEmpty()) {
            for (Long deviceId : deviceIds) {
                alarmRuleInstanceSession.deleteByDeviceId(deviceId);
            }
        }
    }

    @Override
    @MultiTransaction
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE_INSTANCE, allEntries = true)
    public void updateByRule(Long alarmRuleId, List<AlarmRuleInstanceDTO> alarmRuleInstances) throws ObjectNotFoundException {
        List<AlarmRuleInstanceDTO> updateInstances = new ArrayList<>();
        List<Long> delInstanceIds = new ArrayList<>();
        List<AlarmRuleInstance> oldInstances = alarmRuleInstanceSession.getByRuleId(alarmRuleId);
        // 循环新旧两个异常规则实例列表，组建要增、删、改的对象列表
        if (oldInstances !=null && !oldInstances.isEmpty()) {
            if (alarmRuleInstances !=null && !alarmRuleInstances.isEmpty()) {
                for (int i = alarmRuleInstances.size() - 1; i >= 0; i--) {
                    AlarmRuleInstanceDTO newInstance = alarmRuleInstances.get(i);
                    for (int j = oldInstances.size() - 1; j >= 0; j--) {
                        AlarmRuleInstance oldInstance = oldInstances.get(j);
                        if (compareObj(newInstance, oldInstance)) {
                            newInstance.setId(oldInstance.getId());
                            updateInstances.add(0, newInstance);
                            alarmRuleInstances.remove(i);
                            oldInstances.remove(j);
                            break;
                        }
                    }
                }
            }
            oldInstances.forEach(item -> delInstanceIds.add(item.getId()));
        }
        // 删除实例
        for (Long id : delInstanceIds) {
            deleteOne(id);
        }
        // 更新实例
        for (AlarmRuleInstanceDTO updateInstance : updateInstances) {
            updateOne(updateInstance);
        }
        // 新增实例
        if (alarmRuleInstances !=null && !alarmRuleInstances.isEmpty()) {
            for (AlarmRuleInstanceDTO alarmRuleInstance : alarmRuleInstances) {
                createOne(alarmRuleInstance);
            }
        }
    }

    @Override
    @Cacheable(value = CacheConsts.Region.ALARM_RULE_INSTANCE,
            key = "{T(com.yunhe.abnormal.consts.AlarmRuleInstanceKey).getStationOfKey(#stationId, #stationCode)}")
    public List<AlarmCalculation> getCalculationByStation(Long stationId, Long stationCode) {
        return alarmRuleInstanceSession.getAlarmCalculation(stationId, stationCode);
    }

    @Override
    public Map<Long, List<AlarmCalculation>> getPointCalculationMapByStationId(Long stationId) {
        List<AlarmCalculation> list = SpringUtil.getBean(AlarmRuleInstanceService.class).getCalculationByStation(stationId, null);
        Map<Long, List<AlarmCalculation>> pointInstanceMap = new HashMap<>();
        if (list != null) {
            for (AlarmCalculation alarmCalculation : list) {
                List<Long> points = alarmCalculation.getPoints();
                if (points !=null && !points.isEmpty()) {
                    points.forEach(point -> pointInstanceMap.computeIfAbsent(point, v -> new ArrayList<>()).add(alarmCalculation));
                }
            }
        }
        return pointInstanceMap;
    }

    @Override
    public Map<Long, List<AlarmCalculation>> getPointCalculationMapByStationCode(Long stationCode) {
        List<AlarmCalculation> list = SpringUtil.getBean(AlarmRuleInstanceService.class).getCalculationByStation(null, stationCode);
        Map<Long, List<AlarmCalculation>> pointInstanceMap = new HashMap<>();
        if (list != null) {
            for (AlarmCalculation alarmCalculation : list) {
                List<Long> points = alarmCalculation.getPoints();
                if (points != null && ! points.isEmpty()) {
                    points.forEach(point -> pointInstanceMap.computeIfAbsent(point, v -> new ArrayList<>()).add(alarmCalculation));
                }
            }
        }
        return pointInstanceMap;
    }

    @Override
    @Cacheable(value = CacheConsts.Region.ALARM_RULE_INSTANCE,
            key = "{T(com.yunhe.abnormal.consts.AlarmRuleInstanceKey).getStationIdKey(#stationId)}")
    public List<AlarmRuleInstanceDTO> findAlarmRuleInstanceForStation(Long stationId) {
        if (stationId == null) {
            return null;
        }
        List<AlarmRuleInstance> result = alarmRuleInstanceSession.getStationBind(stationId);
        return mapper.to(result);
    }

    @Override
    @Cacheable(value = CacheConsts.Region.ALARM_RULE_INSTANCE,
            key = "{T(com.yunhe.abnormal.consts.AlarmRuleInstanceKey).getDeviceIdKey(#deviceId)}")
    public List<AlarmRuleInstanceDTO> findAlarmRuleInstanceForDevice(Long deviceId) {
        if (deviceId == null) {
            return null;
        }
        List<AlarmRuleInstance> result = alarmRuleInstanceSession.getDeviceBind(deviceId);
        return mapper.to(result);
    }

    @Override
    public List<AlarmRuleInstanceDTO> findByStationId(Long stationId) {
        List<AlarmRuleInstance> alarmRuleInstances =  alarmRuleInstanceRepository.findByStationId(stationId);
        return mapper.to(alarmRuleInstances);
    }

    @Override
    public Long getMaxAlarmVirtualPid(Long stationCode) {
        String prefix = stationCode + "99998";
        String key = CacheConsts.KEY.MAX_VIRTUAL_POINT_NUMBER.value(stationCode);
        if (redisClient.get(key) == null) {
            Long maxPid = alarmRuleInstanceRepository.getMaxAlarmVirtualPid(stationCode);
            if (maxPid == null) {
                maxPid = Long.parseLong(stationCode + "999980");
            }
            redisClient.set(key, String.valueOf(maxPid).replace(prefix, ""), 60);
        }
        return Long.parseLong(prefix + redisClient.incr(key));
    }

    @Override
    public List<AlarmRuleInstanceDTO> findByVirtualPointIds(List<Long> virtualPids) {
        List<AlarmRuleInstance> alarmRuleInstances = alarmRuleInstanceRepository.findByVirtualPointIds(virtualPids);
        return mapper.to(alarmRuleInstances);
    }

    @Override
    public List<AlarmCalculation> getCalculationByInstanceIds(List<Long> instanceIds) {
        return alarmRuleInstanceSession.getAlarmCalculationByInstanceIds(instanceIds);
    }
}
